package cn.admin.remoting.serialize.impl;

import cn.admin.remoting.util.LoggerInit;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

public class SliceOutputStream extends OutputStream {

    protected ByteArrayOutputStream[] bufArray = new SkippableBytesOutputStream[]{
            new SkippableBytesOutputStream()};
    private ByteArrayOutputStream currentSlice;
    protected Map<String,Integer> skips = new HashMap<>();

    public SliceOutputStream() {
        this.currentSlice = this.bufArray[this.bufArray.length - 1];
    }

    @Override
    public void write(int b) throws IOException {
        this.currentSlice.write(b);
    }

    @Override
    public void write(byte[] b) throws IOException {
        this.write(b,0,b.length);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        this.currentSlice.write(b,off,len);
    }

    public int size() {
        int result = 0;
        for (ByteArrayOutputStream buf : this.bufArray) {
            result += buf.size();
        }

        return result;
    }

    public byte[] toByteArray() {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        try {
            for (ByteArrayOutputStream slice : this.bufArray) {
                bout.write(slice.toByteArray());
            }
        } catch (IOException e) {
            LoggerInit.LOGGER.error(e.getMessage(),e);
        }

        return bout.toByteArray();
    }

    public boolean skipInt(String tag) {
        if (this.skips.containsKey(tag)) {
            return false;
        } else {
            this.skips.put(tag,this.bufArray.length - 1);
            return ((SkippableBytesOutputStream)this.currentSlice).skipInt(tag);
        }
    }

    public boolean writeSkippedInt(String tag, int value) {
        Integer idx = this.skips.remove(tag);
        if (null != idx && idx >= 0) {
            SkippableBytesOutputStream tmp = (SkippableBytesOutputStream)this.bufArray[idx];
            return tmp.writeTag(tag, value);
        } else {
            return false;
        }
    }

    public byte[] currentSlice() {
        return this.currentSlice.toByteArray();
    }

    public byte[][] allSlice() {
        byte[][] array = new byte[this.bufArray.length][];
        int i = 0;
        for (ByteArrayOutputStream buf : this.bufArray) {
            array[i ++] = buf.toByteArray();
        }
        return array;
    }

    public int sliceCount() {
        return this.bufArray.length;
    }

    public void addSlice(byte[] slice) {
        int newLength = this.currentSlice.size() == 0 ? this.bufArray.length + 1 : this.bufArray.length + 2;
        ByteArrayOutputStream[] newBufArray = new ByteArrayOutputStream[newLength];
        System.arraycopy(this.bufArray, 0, newBufArray, 0, newLength - 2);
        newBufArray[newLength - 2] = new ByteArrayOutputStreamR(slice);
        newBufArray[newLength - 1] = this.currentSlice = new SkippableBytesOutputStream();
        this.bufArray = newBufArray;
    }

    private static class ByteMath {
        public static byte[] int2bytes(int x) {
            return new byte[]{(byte)(x >> 24), (byte)(x >> 16), (byte)(x >> 8), (byte)(x)};
        }

        public static int bytes2int(byte[] bb, int idx) {
            return (bb[idx] & 255) << 24 | (bb[idx + 1] & 255) << 16 | (bb[idx + 2] & 255) << 8 | (bb[idx + 3] & 255);
        }
    }

    static class ByteArrayOutputStreamR extends ByteArrayOutputStream {

        ByteArrayOutputStreamR(byte[] buf) {
            super(0);
            super.buf = buf;
            super.count = buf.length;
        }

        @Override
        public synchronized void write(int b) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void write(byte[] b) throws IOException {
            throw new UnsupportedOperationException();
        }

        @Override
        public synchronized void write(byte[] b, int off, int len) {
            throw new UnsupportedOperationException();
        }

        @Override
        public synchronized void writeTo(OutputStream out) throws IOException {
            throw new UnsupportedOperationException();
        }

        @Override
        public synchronized void reset() {
            throw new UnsupportedOperationException();
        }
    }

    static class SkippableBytesOutputStream extends ByteArrayOutputStream {
        private static final byte[] EMPTY_4_BYTES = new byte[] {0,0,0,0};
        protected Map<String,Integer> skips = new HashMap<>();

        public synchronized boolean skipInt(String tag) {
            if (this.skips.containsKey(tag)) {
                return false;
            } else {
                this.skips.put(tag,this.count);
                this.write(EMPTY_4_BYTES,0,EMPTY_4_BYTES.length);
                return true;
            }
        }

        public synchronized boolean writeTag(String tag,int value) {
            Integer skipPos = this.skips.remove(tag);
            if (null != skipPos && skipPos >= 0) {
                byte[] tmp = ByteMath.int2bytes(value);
                System.arraycopy(tmp,0,this.buf,skipPos,tmp.length);
                return true;
            } else {
                return false;
            }
        }
    }

}
